home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Graphics Plus
/
Graphics Plus.iso
/
general
/
modelers
/
geomview
/
source.lha
/
Geomview
/
doc
/
geomview-3
< prev
next >
Wrap
Text File
|
1993-12-06
|
51KB
|
1,295 lines
Info file: geomview, -*-Text-*-
produced by texinfo-format-buffer
from file: geomview.tex
File: geomview Node: GROUP, Prev: TLIST, Up: Object File Formats, Next: DISCGRP
GROUP Files
-----------
This format is obsolete, but is still accepted. It combined the
functions of `INST' and `TLIST', taking a series of
transformations and a single Geom (`unit') object, and replicating
the object under each transformation.
GROUP ... < matrices > ... unit { OOGL-OBJECT }
is still accepted and effectively translated into
INST
transforms { TLIST ... <matrices> ... }
unit { OOGL-OBJECT }
File: geomview Node: DISCGRP, Prev: GROUP, Up: Object File Formats, Next: Non-geometric objects
DISCGRP Files
-------------
This format is for discrete groups, such as appear in the theory of
manifolds or in symmetry patterns. This format has its own man page.
See discgrp(5).
File: geomview Node: Non-geometric objects, Prev: DISCGRP, Up: OOGL File Formats, Next: transform
Non-geometric objects
=====================
The syntax of these objects is given in the form used in
*Note References::, where "quoted" items should appear literally but
without quotes, square bracketed ([ ]) items are optional, and | separates
alternative choices.
* Menu:
* transform:: Transformation matrices.
* camera:: Cameras.
* window:: Windows.
File: geomview Node: transform, Prev: Non-geometric objects, Up: Non-geometric objects, Next: camera
Transform Objects
-----------------
Where a single 4x4 matrix is expected -- as in the
`INST' `transform' field, the camera's `camtoworld' transform
and the Geomview `xform*' commands -- use a transform object.
Note that a transform is distinct from a `TLIST', which is a type
of geometry. `TLIST's can contain one or more 4x4 transformations;
"transform" objects must have exactly one.
Why have both? In many places -- e.g. camera positioning -- it's only
meaningful to have a single transform. Using a separate object type
enforces this.
Syntax for a transform object is
<transform> ::=
[ "{" ] (curly brace, generally needed to make
the end of the object unambiguous.)
[ "transform" ] (optional keyword; unnecessary if the type
is determined by the context, which it
usually is.)
[ "define" <name> ]
(defines a transform named <name>, setting
its value from the stuff which follows)
<sixteen floating-point numbers>
(interpreted as a 4x4 homogeneous transform
given row by row, intended to apply to a
row vector multiplied on its LEFT, so that e.g.
Euclidean translations appear in the bottom row)
|
"<" <filename> (meaning: read transform from that file)
|
":" <name> (meaning: use variable <name>,
defined elsewhere; if undefined the initial
value is the identity transform)
[ "}" ] (matching curly brace)
The whole should be enclosed in { braces }. Braces are not essential
if exactly one of the above items is present, so e.g. a 4x4 array of
floats standing alone may but needn't have braces.
Some examples, in contexts where they might be used:
# Example 1: A gcl command to define a transform
# called "fred"
(read transform { transform define fred
1 0 0 0
0 1 0 0
0 0 1 0
-3 0 1 1
}
)
# Example 2: A camera object using transform
# "fred" for camera positioning
# Given the definition above, this puts the camera at
# (-3, 0, 1), looking toward -Z.
{ camera
halfyfield 1
aspect 1.33
camtoworld { : fred }
}
File: geomview Node: camera, Prev: transform, Up: Non-geometric objects, Next: window
cameras
-------
A camera object specifies the following properties of a camera:
position and orientation
specified by either a camera-to-world or world-to-camera transformation;
this transformation does not include the projection, so it's typically
just a combination of translation and rotation. Specified as a
transform object, typically a 4x4 matrix.
"focus" distance
Intended to suggest a typical distance from the camera to the object of
interest; used for default camera positioning (the camera is placed at
(X,Y,Z) = (0,0,focus) when reset) and for adjusting field-of-view when
switching between perspective and orthographic views.
window aspect ratio
True aspect ratio in the sense <Xsize>/<Ysize>. This normally should
agree with the aspect ratio of the camera's window. Geomview normally
adjusts the aspect ratio of its cameras to match their associated
windows.
near and far clipping plane distances
Note that both must be strictly greater than zero. Very large
<far>/<near> distance ratios cause Z-buffering to behave badly; part of
an object may be visible even if somewhat more distant than another.
field of view
Specified in either of two forms.
`fov '
is the field of view -- in degrees if perspective, or linear
distance if orthographic -- in the *shorter* direction.
`halfyfield '
is half the projected Y-axis field, in world coordinates (not angle!),
at unit distance from the camera. For a perspective camera, halfyfield
is related to angular field:
halfyfield = tan( Y_axis_angular_field / 2 )
while for an orthographic one it's simply:
halfyfield = Y_axis_linear_field / 2
This odd-seeming definition is (a) easy to calculate with and
(b) well-defined in both orthographic and perspective views.
The syntax for a camera is:
<camera> ::=
[ "camera" ] (optional keyword)
[ "{" ] (opening brace, generally required)
[ "define" <name> ]
"<" <filename>
|
":" <name>
|
(or any number of the following,
in any order...)
"perspective" {"0" | "1"} (default 1)
(otherwise orthographic)
"stereo" {"0" | "1"} (default 0)
(otherwise mono)
"worldtocam" <transform> (see transform syntax above)
"camtoworld" <transform>
(no point in specifying both
camtoworld and worldtocam; one is
constrained to be the inverse of the other)
"halfyfield" <half-linear-Y-field-at-unit-distance>
(default tan 40/2 degrees)
"fov" (angular field-of-view if perspective,
linear field-of-view otherwise.
Measured in whichever direction is smaller,
given the aspect ratio. When aspect ratio
changes -- e.g. when a window is reshaped --
"fov" is preserved.)
"frameaspect" <aspect-ratio> (X/Y) (default 1.333)
"near" <near-clipping-distance> (default 0.1)
"far" <far-clipping-distance> (default 10.0)
"focus" <focus-distance> (default 3.0)
[ "}" ] (matching closebrace)
File: geomview Node: window, Prev: camera, Up: Non-geometric objects, Next: Customization
window
------
A window object specifies size, position, and other window-system
related information about a window in a device-independent way.
The syntax for a window object is:
window ::=
[ "window" ] (optional keyword)
[ "{" ] (curly brace, often required)
(any of the following, in any order)
"size" <xsize> <ysize>
(size of the window)
"position" <xmin> <xmax> <ymin> <ymax>
(position & size)
"noborder"
(specifies the window should
have no window border)
"pixelaspect" <aspect>
(specifies the true visual aspect ratio
of a pixel in this window in the sense
xsize/ysize, normally 1.0.
For stereo hardware which stretches the
display vertically by a factor of 2,
"pixelaspect 0.5" might do.
The value is used when computing the
projection of a camera associated with
this window.)
[ "}" ] (matching closebrace)
Window objects are used in the Geomview `window' and
`ui-panel' commands to set default properties for future windows or
to change those of an existing window.
File: geomview Node: Customization, Prev: window, Up: Top, Next: Modules
Customization: `.geomview' files
********************************
When Geomview is started, it loads and executes commands in a
system-wide startup file named `.geomview'. This file is in the
`data' subdirectory of the Geomview distribution directory
`/u/gcg/ngrap/data' on the Geometry Center's computer system) and
contains gcl commands to configure Geomview in a way
common to all users on the system.
Next, Geomview looks for the file `~/.geomview' (`~'
stands for your home directory). You can use this to configure
your own default Geomview behavior to suit your tastes.
After reading `~/.geomview', Geomview looks for a file named
`.geomview' in the current directory. If such a file exists
Geomview reads it, unless it is the same as `~/.geomview' (which
would be the case if you are running Geomview from your home directory).
You can use the current directory's `.geomview' to create a Geomview
customization specific to a certain project.
You can use `.geomview' files to control all kinds of things about
Geomview. They can contain any valid gcl statements. Especially
useful is the `ui-panel' command which controls the initial
placement of Geomview's panels. For an example see the system-wide
`.geomview' file mentioned above. For details of gcl,
*Note GCL::.
It is a good idea to enclose all the commands you put in a
`.geomview' file in a `progn' statement in order to cause
Geomview to execute them all at once. Otherwise Geomview might execute
them sequentially over the first few refresh cycles after starting up.
File: geomview Node: Modules, Prev: Customization, Up: Top, Next: Interface
External Modules
****************
An external module is a program that interacts with Geomview. A
module communicates with Geomview through gcl and can
control any apsect of Geomview that you can control through Geomview's
user interface.
In many cases an external module is a specialized program that
implements some mathematical algorithm that creates a geometric
object that changes shape as the algorithm progresses. The module
informs Geomview of the new object shape at each step, so the object
appears to evolve with time in the Geomview window. In this way
Geomview serves as a *display engine* for the module.
An external module may be interactive. It can respond to mouse and
keyboard events that take place in a Geomview window, thus extending
the capability of Geomview itself.
* Menu:
* Interface:: How External Modules Interface with Geomview.
* Example1:: Simple External Module.
* Example2:: Simple External Module with FORMS Control Panel.
* Forms:: The FORMS Library.
* Example3:: External Module with Bi-Directional Communication.
* Module Installation:: Module Installation.
File: geomview Node: Interface, Prev: Modules, Up: Modules, Next: Example1
How External Modules Interface with Geomview
============================================
External modules appear in the *Modules* browser in Geomview's
*Main* panel. To run a module, click the left mouse button on
the module's entry in the browser. While the module is running, an
additional line for that module will appear in red in the browser.
This line begins with a number in brackets, which indicates the
*instace* number of the module. (For some modules it makes sense
to have more than one instance of the module running at the same
time.) You can kill an external module by clicking on its red
instance entry.
By default when Geomview starts, it displays all the modules that have
been installed on your system.
For instructions on installing a module on your system so that it will
appear in the *Modules* browser every time Geomview is run by
anyone on your system, *Note Module Installation::.
When Geomview invokes an external module, it creates pipes connected
to the module's standard input and output. (Pipes are like files
except they are used for communication between programs rather than
for storing things on a disk.) Geomview interprets anything that the
module writes to its standard output as a gcl command.
Likewise, if the exernal module requests any data from Geomview,
Geomview writes that data to the module's standard input. Thus all a
module has to do in order to communicate with Geomview is write
commands to standard output and (optionally) receive data on standard
input. Note that this means that the module cannot use standard input
and output for communicating with the user. If a module needs to
communicate with the user it can do so either through a control
panel of its own or else by responding to certain events that it finds
out about from Geomview.
File: geomview Node: Example1, Prev: Interface, Up: Modules, Next: Example2
Example 1: Simple External Module
=================================
This section gives a very simple external module which displays an
oscillating mesh. To try out this example, make a copy of the file
`example1.c' (it is distributed with Geomview in the `doc'
subdirectory) in your directory and compile it with the command
cc -o example1 example1.c -lm
Then put the line
(emodule-define "Example 1" "./example1")
in a file called `.geomview' in your current directory. Then invoke
Geomview; it is important that you compile the example program, create
the `.geomview' file, and invoke Geomview all in the same
directory. You should see "Example 1" in the *Modules* browser
of Geomview's *Main* panel; click on this entry in the browser to
start the module. A surface should appear in your camera window and should
begin oscillating. You can stop the module by clicking on the red "[1]
Example 1" line in the *Modules* browser.
/*
* example1.c: oscillating mesh
*
* This example module is distributed with the Geomview manual.
* If you are not reading this in the manual, see the "External
* Modules" chapter of the manual for more details.
*
* This module creates an oscillating mesh.
*/
#include <math.h>
#include <stdio.h>
/* F is the function that we plot
*/
float F(x,y,t)
float x,y,t;
{
float r = sqrt(x*x+y*y);
return(sin(r + t)*sqrt(r));
}
main(argc, argv)
char **argv;
{
int xdim, ydim;
float xmin, xmax, ymin, ymax, dx, dy, t, dt;
xmin = ymin = -5; /* Set x and y */
xmax = ymax = 5; /* plot ranges */
xdim = ydim = 24; /* Set x and y resolution */
dt = 0.1; /* Time increment is 0.1 */
/* Geomview setup. We begin by sending the command
* (geometry example { : foo})
* to Geomview. This tells Geomview to create a geom called
* "example" which is an instance of the handle "foo".
*/
printf("(geometry example { : foo })\n");
fflush(stdout);
/* Loop until killed.
*/
for (t=0; ; t+=dt) {
UpdateMesh(xmin, xmax, ymin, ymax, xdim, ydim, t);
}
}
/* UpdateMesh sends one mesh iteration to Geomview. This consists of
* a command of the form
* (read geometry { define foo
* MESH
* ...
* })
* where ... is the actual data of the mesh. This command tells
* Geomview to make the value of the handle "foo" be the specified
* mesh.
*/
UpdateMesh(xmin, xmax, ymin, ymax, xdim, ydim, t)
float xmin, xmax, ymin, ymax, t;
int xdim, ydim;
{
int i,j;
float x,y, dx,dy;
dx = (xmax-xmin)/(xdim-1);
dy = (ymax-ymin)/(ydim-1);
printf("(read geometry { define foo \n");
printf("MESH\n");
printf("%1d %1d\n", xdim, ydim);
for (j=0, y = ymin; j<ydim; ++j, y += dy) {
for (i=0, x = xmin; i<xdim; ++i, x += dx) {
printf("%f %f %f\t", x, y, F(x,y,t));
}
printf("\n");
}
printf("})\n");
fflush(stdout);
}
The module begins by defining a function `F(x,y,t)' that
specifies a time-varying surface. The purpose of the module is to
animate this surface over time.
The main program begins by defining some variables that specify
the parameters with which the function is to be plotted.
The next bit of code in the main program prints the following
line to standard output
(geometry example { : foo })
This tells Geomview to create a geom called `example' which is an
instance of the handle `foo'. *Handles* are a part of the
OOGL file format which allow you to name a piece of geometry whose value
can be specified elsewhere (and in this case updated many times); for
more information on handles, *Note OOGL File Formats:: In this case,
`example' is the title by which the user will see the object in
Geomview's object browser, and `foo' is the internal name of the
handle that the object is a reference to.
We then do `fflush(stdout)' to ensure that Geomview
receives this command immediately. In general, since pipes may be
buffered, an external module should do this whenever it wants to be
sure Geomview has actually received everything it has printed out.
The last thing in the main program is an infinite loop that cycles
through calls to the procedure `UpdateMesh' with increasing
values of `t'. `UpdateMesh' sends Geomview a command
of the form
(read geometry { define foo
MESH
24 24
...
})
where `...' is a long list of numbers. This command tells Geomview
to make the value of the handle `foo' be the specified mesh. As
soon as Geomview receives this command, the geom being displayed
changes to reflect the new geometry.
The mesh is given in the format of an OOGL MESH. This begins with
the keyword `MESH'. Next come two numbers that give the x and y
dimensions of the mesh; in this case they are both 24. This line is
followed by 24 lines, each containing 24 triples of numbers. Each of
these triples is a point on the surface. Then finally there is a line
with "`})'" on it that ends the "`{'" which began the
`define' statement and the "`('" that began the command. For
more details on the format of MESH data, *Note MESH::.
This module could be written without the use of handles by having it
write out commands of the form
(geometry example {
MESH
24 24
...
})
This first time Geomview receives a command of this form it would create
a geom called `example' with the given `MESH' data.
Subsequent `(geometry example ...)' commands would cause
Geomview to replace the geometry of the geom `example' with the new
`MESH' data. If done in this way there would be no need to send
the initial `(geometry example { : foo })' command as above. The
handle technique is useful, however, because it can be used in more
general situations where a handle represents only part of a complex
geom, allowing an external module to replace only that part without
having to retransmit the entire geom. For more information on handles,
*Note GCL::.
The module loops through calls to `UpdateMesh' which print out
commands of the above form one after the other as fast as possible.
The loop continues indefinitely; the module will terminate when the
user kills it by clicking on its instance line in the *Modules*
browser, or else when Geomview exits.
Sometimes when you terminate this module by clicking on its instance
entry the *Modules* browser, Geomview will kill it while it is in
the middle of sending a command to Geomview. Geomview will then receive
only a piece of a command and will print out a cryptic but harmless
error message about this. When a module has a user interface panel
it can use a "Quit" button to provide a more graceful way for the user
to terminate the module. See the next example.
You can run this module in a shell window without Geomview to see the
commands it prints out. You will have to kill it with
`ctrl-C' to get it to stop.
File: geomview Node: Example2, Prev: Example1, Up: Modules, Next: Forms
Example 2: Simple External Module with FORMS Control Panel
==========================================================
This section gives a new version of the above module --- one that
includes a user interface panel for controlling the velocity of the
oscillation. We use the FORMS library by Mark Overmars for the control
panel. The FORMS library is a public domain user interface toolkit for
IRISes; for more information *Note Forms::.
To try out this example, make a copy of the file
`example2.c' (distributed with Geomview in the `doc'
subdirectory) in your directory and compile it with the command
cc -I/u/gcg/ngrap/include -o example2 example2.c \
-L/u/gcg/ngrap/lib/sgi -lforms -lfm_s -lgl_s -lm
If you are not using the Geometry Center's computer system you should
replace the string `/u/gcg/ngrap' above with the pathname of the
Geomview distribution directory on your system. (The forms library is
distributed with Geomview and the `-I' and `-L' options above
tell the compiler where to find it.)
Then put the line
(emodule-define "Example 2" "./example2")
in a file called `.geomview' in the current directory and invoke
Geomview from that directory. Click on the "Example 2" entry in the
*Modules* browser to invoke the module. A small control panel
should appear. You can then control the velocity of the mesh
oscillation by moving the slider.
/*
* example2.c: oscillating mesh with FORMS control panel
*
* This example module is distributed with the Geomview manual.
* If you are not reading this in the manual, see the "External
* Modules" chapter of the manual for an explanation.
*
* This module creates an oscillating mesh and has a FORMS control
* panel that lets you change the speed of the oscillation with a
* slider.
*/
#include <math.h>
#include <stdio.h>
#include <sys/time.h> /* for struct timeval below */
#include "forms.h" /* for FORMS library */
FL_FORM *OurForm;
FL_OBJECT *VelocitySlider;
float dt;
/* F is the function that we plot
*/
float F(x,y,t)
float x,y,t;
{
float r = sqrt(x*x+y*y);
return(sin(r + t)*sqrt(r));
}
/* SetVelocity is the slider callback procedure; FORMS calls this
* when the user moves the slider bar.
*/
void SetVelocity(FL_OBJECT *obj, long val)
{
dt = fl_get_slider_value(VelocitySlider);
}
/* Quit is the "Quit" button callback procedure; FORMS calls this
* when the user clicks the "Quit" button.
*/
void Quit(FL_OBJECT *obj, long val)
{
exit(0);
}
/* create_form_OurForm() creates the FORMS panel by calling a bunch of
* procedures in the FORMS library. This code was generated
* automatically by the FORMS designer program; normally this code
* would be in a separate file which you would not edit by hand. For
* simplicity of this example, however, we include this code here.
*/
create_form_OurForm()
{
FL_OBJECT *obj;
FL_FORM *form;
OurForm = form = fl_bgn_form(FL_NO_BOX,380.0,120.0);
obj = fl_add_box(FL_UP_BOX,0.0,0.0,380.0,120.0,"");
VelocitySlider = obj = fl_add_valslider(FL_HOR_SLIDER,20.0,30.0,
340.0,40.0,"Velocity");
fl_set_object_lsize(obj,FL_LARGE_FONT);
fl_set_object_align(obj,FL_ALIGN_TOP);
fl_set_call_back(obj,SetVelocity,0);
obj = fl_add_button(FL_NORMAL_BUTTON,290.0,75.0,70.0,35.0,"Quit");
fl_set_object_lsize(obj,FL_LARGE_FONT);
fl_set_call_back(obj,Quit,0);
fl_end_form();
}
main(argc, argv)
char **argv;
{
int xdim, ydim;
float xmin, xmax, ymin, ymax, dx, dy, t;
int fdmask;
static struct timeval timeout = {0, 200000};
xmin = ymin = -5; /* Set x and y */
xmax = ymax = 5; /* plot ranges */
xdim = ydim = 24; /* Set x and y resolution */
dt = 0.1; /* Time increment is 0.1 */
/* Forms panel setup.
*/
foreground();
create_form_OurForm();
fl_set_slider_bounds(VelocitySlider, 0.0, 1.0);
fl_set_slider_value(VelocitySlider, dt);
fl_show_form(OurForm, FL_PLACE_SIZE, TRUE, "Example 2");
/* Geomview setup.
*/
printf("(geometry example { : foo })\n");
fflush(stdout);
/* Loop until killed.
*/
for (t=0; ; t+=dt) {
fdmask = (1 << fileno(stdin)) | (1 << qgetfd());
select(qgetfd()+1, &fdmask, NULL, NULL, &timeout);
fl_check_forms();
UpdateMesh(xmin, xmax, ymin, ymax, xdim, ydim, t);
}
}
/* UpdateMesh sends one mesh iteration to Geomview
*/
UpdateMesh(xmin, xmax, ymin, ymax, xdim, ydim, t)
float xmin, xmax, ymin, ymax, t;
int xdim, ydim;
{
int i,j;
float x,y, dx,dy;
dx = (xmax-xmin)/(xdim-1);
dy = (ymax-ymin)/(ydim-1);
printf("(read geometry { define foo \n");
printf("MESH\n");
printf("%1d %1d\n", xdim, ydim);
for (j=0, y = ymin; j<ydim; ++j, y += dy) {
for (i=0, x = xmin; i<xdim; ++i, x += dx) {
printf("%f %f %f\t", x, y, F(x,y,t));
}
printf("\n");
}
printf("})\n");
fflush(stdout);
}
The code begins by including some header files needed for the event loop
and the FORMS library. It then declares global variables for holding a
pointer to the slider FORMS object and the velocity `dt'. These
are global because they are needed in the slider callback procedure
`SetVelocity', which forms calls every time the user moves the
slider bar. `SetVelocity' sets `dt' to be the new value of the
slider.
`Quit' is the callback procedure for the *Quit* button;
it provides a graceful way for the user to terminate the program.
The procedure `create_panel' calls a bunch of FORMS library
procedures to set up the control panel with slider and button. For more
information on using FORMS to create interface panels see the FORMS
documentation. In particular, FORMS comes with a graphical panel
designer that lets you design your panels interactively and generates
code like that in `create_panel'.
This example's main program is similar to the previous example, but
includes extra code to deal with setting up and managing the FORMS
panel.
To set up the panel we call the GL procedure `foreground' to cause
the process to run in the foreground. By default GL programs run in the
background, and for various reasons external modules that use FORMS
(which is based on GL) need to run in the foreground. We then call
`create_panel' to create the panel and `fl_set_slider_value'
to set the initial value of the slider. The call to `fl_show_form'
causes the panel to appear on the screen.
The first three lines of the main loop, starting with
fdmask = (1 << fileno(stdin)) | (1 << qgetfd());
check for and deal with events in the panel. The call to `select'
imposes a delay on each pass through the main loop. This call returns
either after a delay of 1/5 second or when the next GL event occurs, or
when data appears on standard input, whichever comes first. The
`timeout' variable specifies the amount of time to wait on this
call; the first member (0 in this example) gives the number of seconds,
and the second member (200000 in this example) gives the number of
microseconds. Finally, `fl_check_forms()' checks for and processes
any FORMS events that have happened; in this case this means calling
`SetVelocity' if the user has moved the slider or calling
`Quit' if the user has clicked on the *Quit* button.
The purpose of the delay in the loop is to keep the program from using
excessive amounts of CPU time running around its main loop when there
are no events to be processed. This is not so crucial in this example,
and in fact may actually slow down the animation somewhat, but in
general with external modules that have event loops it is important to
do something like this because otherwise the module will needlessly take
CPU cycles away from other running programs (such as Geomview!) even
when it isn't doing anything.
The last line of the main loop in this example, the call to
`UpdateMesh', is the same as in the previous example.
File: geomview Node: Forms, Prev: Example2, Up: Modules, Next: Example3
The FORMS Library
=================
Geomview itself is written using Mark Overmar's public domain FORMS
library. FORMS is a handy and relatively simple user interface toolkit
for IRISes. Many Geomview external modules, including the examples in
this manual, use FORMS to create and manage control panels.
We distribute a version of the FORMS library with Geomview because it is
necessary in order to compile Geomview and many of our modules. If you
use FORMS to write Geomview modules (or anything else, for that matter)
you may use this copy. The header file `forms.h' is in the
`include' subdirectory, and the library file `libforms.a' is
in the `lib/sgi' subdirectory (these are subdirectories of the
Geomview distribution directory, `/u/gcg/ngrap' on the Geometry
Center's system). In particular, you can link the example modules in
this manual using this copy.
FORMS is available via ftp on the Internet from a variety of sites,
including `cs.ruu.nl' or `glaurung.physics.mcgill.ca'. It
comes with source code and extensive documentation.
If you wish you may use any other interface toolkit instead of FORMS in
an external module. We chose FORMS because it is free and relatively
simple.
File: geomview Node: Example3, Prev: Forms, Up: Modules, Next: Module Installation
Example 3: External Module with Bi-Directional Communication
============================================================
The previous two example modules simply send commands to Geomview and do
not receive anything from Geomview. This section describes a module
that communicates in both directions. There are two types of
communication that can go from Geomview to an external module. This
example shows *asynchronous* communication --- the module needs to
be able to respond at any moment to expressions that Geomview may emit
which inform the module of some change of state within Geomview.
(The other type of communication is *synchronous*, where a module
sends a request to Geomview for some piece of information and waits for
a response to come back before doing anything else. The main gcl
command for requesting information of this type is `write'. This
module does not do any synchronous communication.)
In ansynchronous communication, Geomview sends expressions that are
essentially echoes of gcl commands. The external module sends
Geomview a command expressing interest in a certain command, and then
every time Geomview executes that command, the module receives a copy of
it. This happens regardless of who sent the command to Geomview; it can
be the result of the user doing something with a Geomview panel, or
it may have come from another module or from a file that Geomview reads.
This is how a module can find out about and act on things that happen in
Geomview.
This example uses the OOGL lisp library to parse and act on the
expressions that Geomview writes to the module's standard input. This
library is actually part of Geomview itself --- we wrote the library in
the process of implementing gcl. It is also convenient to use it in
external modules that must understand a of subset of gcl ---
specifically, those commands that the module has expressed interest in.
This example shows how a module can receive user pick events, i.e.
when the user clicks the right mouse button with the cursor over a geom
in a Geomview camera window. When this happens Geomview generates an
internal call to a procedure called `pick'; the arguments to the
procedure give information about the pick, such as what object was
picked, the coordinates of the picked point, etc. If an external module
has expressed interest in calls to `pick', then whenever
`pick' is called Geomview will echo the call to the module's
standard input. The module can then do whatever it wants with the pick
information.
This module is the same as the *Nose* module that comes with
Geomview. Its purpose is to illustrate picking. Whenever you pick on a
geom by clicking the right mouse button on it, the module draws a little
box at the spot where you clicked. Usually the box is yellow. If you
pick a vertex, the box is colored magenta. If you pick a point on an
edge of an object, the module will also highlight the edge by drawing
cyan boxes at its endpoints and drawing a yellow line along the edge.
Note that in order for this module to actually do anything you must have
a geom loaded into Geomview and you must click the right mouse button
with the cursor over a part of the geom.
/*
* example3.c: external module with bi-directional communication
*
* This example module is distributed with the Geomview manual.
* If you are not reading this in the manual, see the "External
* Modules" chapter of the manual for an explanation.
*
* This module is the same as the "Nose" program that is distributed
* with Geomview. It illustrates how a module can find out about
* and respond to user pick events in Geomview. It draws a little box
* at the point where a pick occurrs. The box is yellow if it is not
* at a vertex, and magenta if it is on a vertex. If it is on an edge,
* the program also marks the edge.
*
* To compile:
*
* cc -I/u/gcg/ngrap/include -g -o example3 example3.c \
* -L/u/gcg/ngrap/lib/sgi -loogl -lm
*
* If you are not on the Geometry Center's system you should replace
* "/u/gcg/ngrap" above with the pathname of the Geomview distribution
* directory on your system.
*/
#include <stdio.h>
#include "lisp.h" /* We use the OOGL lisp library */
#include "pickfunc.h" /* for PICKFUNC below */
#include "3d.h" /* for 3d geometry library */
/* boxstring gives the OOGL data to define the little box that
* we draw at the pick point. NOTE: It is very important to
* have a newline at the end of the OFF object in this string.
*/
char boxstring[] = "\
INST\n\
transform\n\
.04 0 0 0\n\
0 .04 0 0\n\
0 0 .04 0\n\
0 0 0 1\n\
geom\n\
OFF\n\
8 6 12\n\
\n\
-.5 -.5 -.5 # 0 \n\
.5 -.5 -.5 # 1 \n\
.5 .5 -.5 # 2 \n\
-.5 .5 -.5 # 3 \n\
-.5 -.5 .5 # 4 \n\
.5 -.5 .5 # 5 \n\
.5 .5 .5 # 6 \n\
-.5 .5 .5 # 7 \n\
\n\
4 0 1 2 3\n\
4 4 5 6 7\n\
4 2 3 7 6\n\
4 0 1 5 4\n\
4 0 4 7 3\n\
4 1 2 6 5\n";
progn()
{
printf("(progn\n");
}
endprogn()
{
printf(")\n");
fflush(stdout);
}
Initialize()
{
extern LObject *Lpick(); /* This is defined by PICKFUNC below but must */
/* be used in the following LDefun() call */
LInit();
LDefun("pick", Lpick, NULL);
progn(); {
/* Define handle "littlebox" for use later
*/
printf("(read geometry { define littlebox { %s }})\n", boxstring);
/* Express interest in pick events; see Geomview manual for explanation.
*/
printf("(interest (pick world * * * * nil nil nil nil nil))\n");
/* Define "pick" object, initially the empty list (= null object).
* We replace this later upon receiving a pick event.
*/
printf("(geometry \"pick\" { LIST } )\n");
/* Make the "pick" object be non-pickable.
*/
printf("(pickable \"pick\" no)\n");
/* Turn off normalization, so that our pick object will appear in the
* right place.
*/
printf("(normalization \"pick\" none)\n");
/* Don't draw the pick object's bounding box.
*/
printf("(bbox-draw \"pick\" off)\n");
} endprogn();
}
/* The following is a macro call that defines a procedure called
* Lpick(). The reason for doing this in a macro is that that macro
* encapsulates a lot of necessary stuff that would be the same for
* this procedure in any program. If you write a Geomview module that
* wants to know about user pick events you can just copy this macro
* call and change the body to suit your needs; the body is the last
* argument to the macro and is delimited by curly braces.
*
* The first argument to the macro is the name of the procedure to
* be defined, "Lpick".
*
* The next two arguments are numbers which specify the sizes that
* certain arrays inside the body of the procedure should have.
* These arrays are used for storing the face and path information
* of the picked object. In this module we don't care about this
* information so we declare them to have length 1, the minimum
* allowed.
*
* The last argument is a block of code to be executed when the module
* receives a pick event. In this body you can refer to certain local
* variables that hold information about the pick. For details see
* Example 3 in the Extenal Modules chapter of the Geomview manual.
*/
PICKFUNC(Lpick, 1, 1,
{
handle_pick(pn>0, &point, vn>0, &vertex, en>0, edge);
})
handle_pick(picked, p, vert, v, edge, e)
int picked; /* was something actually picked? */
int vert; /* was the pick near a vertex? */
int edge; /* was the pick near an edge? */
HPoint3 *p; /* coords of pick point */
HPoint3 *v; /* coords of picked vertex */
HPoint3 e[2]; /* coords of endpoints of picked edge */
{
Normalize(&e[0]); /* Normalize makes 4th coord 1.0 */
Normalize(&e[1]);
Normalize(p);
progn(); {
if (!picked) {
printf("(geometry \"pick\" { LIST } )\n");
} else {
/*
* Put the box in place, and color it magenta if it's on a vertex,
* yellow if not.
*/
printf("(xform-set pick { 1 0 0 0 0 1 0 0 0 0 1 0 %g %g %g 1 })\n",
p->x, p->y, p->z);
printf("(geometry \"pick\"\n");
if (vert) printf("{ appearance { material { diffuse 1 0 1 } }\n");
else printf("{ appearance { material { diffuse 1 1 0 } }\n");
printf(" { LIST { :littlebox }\n");
/*
* If it's on an edge and not a vertex, mark the edge
* with cyan boxes at the endpoins and a black line
* along the edge.
*/
if (edge && !vert) {
e[0].x -= p->x; e[0].y -= p->y; e[0].z -= p->z;
e[1].x -= p->x; e[1].y -= p->y; e[1].z -= p->z;
printf("{ appearance { material { diffuse 0 1 1 } }\n\
LIST\n\
{ INST transform 1 0 0 0 0 1 0 0 0 0 1 0 %f %f %f 1 geom :littlebox }\n\
{ INST transform 1 0 0 0 0 1 0 0 0 0 1 0 %f %f %f 1 geom :littlebox }\n\
{ VECT\n\
1 2 1\n\
2\n\
1\n\
%f %f %f\n\
%f %f %f\n\
1 1 0 1\n\
}\n\
}\n",
e[0].x, e[0].y, e[0].z,
e[1].x, e[1].y, e[1].z,
e[0].x, e[0].y, e[0].z,
e[1].x, e[1].y, e[1].z);
}
printf(" }\n }\n)\n");
}
} endprogn();
}
Normalize(HPoint3 *p)
{
if (p->w != 0) {
p->x /= p->w;
p->y /= p->w;
p->z /= p->w;
p->w = 1;
}
}
main()
{
Lake *lake;
LObject *lit, *val;
extern char *getenv();
Initialize();
lake = LakeDefine(stdin, stdout, NULL);
while (!feof(stdin)) {
/* Parse next lisp expression from stdin.
*/
lit = LSexpr(lake);
/* Evaluate that expression; this is where Lpick() gets called.
*/
val = LEval(lit);
/* Free the two expressions from above.
*/
LFree(lit);
LFree(val);
}
}
The code begins by defining procedures `progn()' and
`endprogn()' which begin and end a Geomview `progn' group.
The purpose of the Geomview `progn' command is to group commands
together and cause Geomview to execute them all at once, without
refreshing any graphics windows until the end. It is a good idea to
group blocks of commands that a module sends to Geomview like this so
that the user sees their cumulative effect all at once.
Procedure `Initialize()' does various things needed at program
startup time. It initializes the lisp library by calling
`LInit()'. Any program that uses the lisp library should call this
once before calling any other lisp library functions. It then calls
`LDefun' to tell the library about our `pick' procedure, which
is defined further down with a call to the `DEFPICKFUNC' macro.
Then it sends a bunch of setup commands to Geomview, grouped in a
`progn' block. This includes defining a handle called `littlebox'
that stores the geometry of the little box. Next it sends the command
(interest (pick world * * * * nil nil nil nil nil))
which tells Geomview to notify us when a pick event happens.
The syntax of this `interest' statement merits some explanation.
In general `interest' takes one argument which is a (parenthesized)
expression representing a Geomview function call. It specifies a type
of call that the module is interested in knowing about. The arguments
can be any particular argument values, or the special symbols `*'
or `nil'. For example, the first argument in the `pick'
expression above is `world'. This means that the module is
interested in calls to `pick' where the first argument, which
specifies the coordinate system, is `world'. A `*' is like a
wild-card; it means that the module is interested in calls where the
corresponding argument has any value. The word `nil' is like
`*', except that the argument's value is not reported to the
module. This is useful for cutting down on the amount of data that must
be transmitted in cases where there are arguments that the module
doesn't care about.
The second, third, fourth, and fifth arguments to the `pick'
command give the name, pick point coordinates, vertex coordinates, and
edge coordinates of a pick event. We specify these by `*''s above.
The remaining five arguments to the `pick' command give other
information about the pick event that we do not care about in this
module, so we specify these with `nil''s. For the details of the
arguments to `pick', *Note GCL::.
The `geometry' statement defines a geom called `pick' that is
initially an empty list, specified as ` { LIST } '; this is the
best way of specifying a null geom. The module will replace this with
something useful by sending Geomview another `geometry' command
when the user picks something. Next we arrange for the `pick'
object to be non-pickable, and turn normalization off for it so that
Geomview will display it in the size and location where we put it,
rather than resizing and relocating it to fit into the unit cube.
The next function in the file, `Lpick', is defined with a strange
looking call to a macro called `PICKFUNC', defined in the header
file `pickfunc.h'. This is the function for handling pick events.
The reason we provide a macro for this is that that macro encapsulates a
lot of necessary stuff that would be the same for the pick-handling
function in any program. If you write a Geomview module that wants to
know about user pick events you can just copy this macro call and change
it to suit yours needs.
In general the syntax for `PICKFUNC' is
PICKFUNC(NAME, MAXFACEVERTS, MAXPATHLEN, BLOCK)
where NAME is the name of the procedure to be defined, in this
case `Lpick'. The next two arguments, MAXFACEVERTS and
MAXPATHLEN, give the sizes to be used for declaring two local
variable arrays in the body of the procedure. These arrays are for
storing information about the picked face and the picked primitive's
path. In this module we don't care about this information (it
corresponds to some of the things masked out by the `nil''s in the
`interest' call above) so we specify 1, the minimum allowable, for
both of these. The last argument, BLOCK, is a block of code to be
executed when a pick event occurs. The BLOCK should be delimited
by curly braces. The code in your BLOCK should not include
any `return' statements.
`PICKFUNC' declares certain local variables in the body of the
procedure. When the module receives a `(pick ...)' statement
from Geomview, the procedure assigns values to these variables based on
the information in the `pick' call. (Variables corresponding to
`nil''s in the `(interest (pick ...))' are not given
values.)
These variables are:
`char *coordsys;'
A string specifying the coordinate system in which coordinates are
given. In this example, this will always be `world' because
of the `interest' call above.
`char *id;'
A string specifying the name of the picked geom.
`HPoint3 point; int pn;'
`point' is an `HPoint3' structure giving the coordinates of
the picked point. `HPoint3' is a homogeneous point coordinate
representation eqivalent to an array of 4 floats. `pn' tells how
many coordinates have been written into this array; it will always be
either 0 or 4. A value of zero means no point was picked, i.e. the user
clicked the right mouse button while the cursor was not pointing at a
geom.
`HPoint3 vertex; int vn;'
`vertex' is an `HPoint3' structure giving the coordinates of
the picked vertex, if the pick point was near a vertex. `vn' tells
how many coordinates have been written into this array; it will always
be either 0 or 4. A value of zero means the pick point was not near a
vertex.
`HPoint3 edge[2]; int en;'
`edge' is an array of two `HPoint3' structures giving the
coordinates of the endpoints of the picked edge, if the pick point was
near an edge. `en' tells how many coordinates have been written
into this array; it will always be either 0 or 8. A value of zero means
the pick point was not near an edge.
In this example module, the remaining variables will never be given
values because their values in the `interest' statement were
specified as `nil'.
`HPoint3 face[MAXFACEVERTS]; int fn;'
`face' is an array of MAXFACEVERTS `HPoint3''s;
MAXFACEVERTS is the value specified in the `PICKFUNC' call.
`face' gives the coordinates of the vertices of the picked face.
`fn' tells how many coordinates have been written into this array;
it will always be a multiple of 4 and will be at most
4*MAXFACEVERTS. A value of zero means the pick point was not near
a face.
`HPoint3 ppath[MAXPATHLEN; int ppn;'
`ppath' is an array of MAXPATHLEN `int''s;
MAXPATHLEN is the value specified in the `PICKFUNC' call.
`ppath' gives the path through the OOGL heirarchy to the picked
primitive. `pn' tells how many integers have been written into
this array; it will be at most MAXPATHLEN. A path of {3,1,2},
for example, means that the picked primitive is "subobject number 2
of subobject number 1 of object 3 in the world".
`int vi;'
`vi' gives the index of the picked vertex in the picked primitive,
if the pick point was near a vertex.
`int ei[2]; int ein'
The `ei' array gives the indices of the endpoints of the picked
edge, if the pick point was near a vertex. `ein' tells how many
integers were written into this array. It will always be either 0 or 2;
a value of 0 means the pick point was not near an edge.
`int fi;'
`fi' gives the index of the picked face in the picked primitive, if
the pick point was near a face.
The `handle_pick' procedure actually does the work of dealing with
the pick event. It begins by normalizing the homogeneous coordinates
passed in as arguments so that we can assume the fourth coordinate is 1.
It then sends gcl commands to define the `pick' object to be
whatever is appropriate for the kind of pick recieved. See *Note OOGL File Formats::, and *Note GCL::, for an explanation of the
format of the data in these commands.
The main program, at the bottom of the file, first calls
`Initialize()'. Next, the call to `LakeDefine' defines the
`Lake' that the lisp library will use. A `Lake' is a
structure that the lisp library uses internally as a type of
communiation vehicle. (It is like a unix stream but more general, hence
the name.) This call to `LakeDefine' defines a `Lake'
structure for doing I/O with `stdin' and `stdout'. The third
argument to `LakeDefine' should be `NULL' for external modules
(it is used by Geomview). Finally, the program enters its main loop
which parses and evaluates expressions from standard input.